﻿using UnityEngine;
using System.Collections;

// Awesome ref: http://docs.unity3d.com/Documentation/Components/SL-Reference.html

[ExecuteInEditMode]
public class TerrainGenerator : MonoBehaviour 
{
    Texture2D lastMap = null;
    /// <summary>
    /// Remember to make sure your height map is readble and that it is automatic true color, see
    /// the import settings for the texture in the Unity Editor.
    /// </summary>
    public Texture2D HeightMap;

    float lastmin = 0;
    float lastmax = 30;
    public float min = 0;
    public float max = 30;

    float lasttxMod = 4.25f;
    public float txMod = 4.25f;

    int myWidth;
    int myHeight;
    float[,] height;

    float dirtLastBlend = 0;
    public float DirtBlend = 0;
    
    float dirtLastHeight = 8;
    public float DirtHeight = 8;

    float grassLastBlend = 12;
    public float GrassBlend = 12;

    float grassLastHeight = 6;
    public float GrassHeight = 6;
    
    public float StoneBlend = 20;
    float stoneLastBlend = 20;

    public float StoneHeight = 6;
    float stoneLastHeight = 6;

    public float SnowBlend = 30;
    float snowLastBlend = 30;

    public float SnowHeight = 10;
    float snowLastHeight = 10;

    void Awake()
    {
        GenerateTerrain();
    }
    void SetLast()
    {
        lastMap = HeightMap;
        lastmin = min;
        lastmax = max;
        lasttxMod = txMod;

        dirtLastBlend = DirtBlend;
        dirtLastHeight = DirtHeight;
        
        grassLastBlend = GrassBlend;
        grassLastHeight = GrassHeight;
        
        stoneLastBlend = StoneBlend;
        stoneLastHeight = StoneHeight;

        snowLastBlend = SnowBlend;
        snowLastHeight = SnowHeight;
    }

    
    void GenerateTerrain()
    {
        MeshFilter meshFilter = GetComponent<MeshFilter>();

        meshFilter.sharedMesh = new Mesh();

        if (HeightMap != null)
        {
            myWidth = HeightMap.width;
            myHeight = HeightMap.height;

            Vector3 posOffset = new Vector3(transform.position.x - (myWidth / 2), 0, transform.position.z - (myHeight / 2));

            Color[] heightMapCol = new Color[myWidth * myHeight];

            Vector3[] verts = new Vector3[myWidth * myHeight];
            Vector3[] normals = new Vector3[myWidth * myHeight];
            Vector2[] uv = new Vector2[myWidth * myHeight];
            Vector4[] tan = new Vector4[myWidth * myHeight];
            Color[] col = new Color[myWidth * myHeight];

            height = new float[myWidth, myHeight];

            heightMapCol = HeightMap.GetPixels();


            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                    height[x, y] = Mathf.Lerp(min, max, heightMapCol[x + y * myWidth].r);


            // Verts
            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                {
                    verts[x + y * myWidth] = posOffset + new Vector3(y, height[x, y], x);
                    normals[x + y * myWidth] = Vector3.up;
                    uv[x + y * myWidth] = new Vector2((float)x / (myWidth / txMod), (float)y / (myHeight / txMod));

                    // blend
                    col[x + y * myWidth].r = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - DirtBlend) / DirtHeight, 0, 1);
                    col[x + y * myWidth].g = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - GrassBlend) / GrassHeight, 0, 1);
                    col[x + y * myWidth].b = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - StoneBlend) / StoneHeight, 0, 1);
                    col[x + y * myWidth].a = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - SnowBlend) / SnowHeight, 0, 1);
                    
                    float totalWeight = col[x + y * myWidth].r;
                    totalWeight += col[x + y * myWidth].g;
                    totalWeight += col[x + y * myWidth].b;
                    totalWeight += col[x + y * myWidth].a;

                    //col[x + y * myWidth].r /= totalWeight;
                    //col[x + y * myWidth].g /= totalWeight;
                    //col[x + y * myWidth].b /= totalWeight;
                    //col[x + y * myWidth].a /= totalWeight;

                    totalWeight *= 2;

                    col[x + y * myWidth] /= totalWeight;
                }


            // Calc normals
            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                {
                    // Tangent Data.
                    if (x != 0 && x < myWidth - 1)
                        tan[x + y * myWidth] = verts[x - 1 + y * myWidth] - verts[x + 1 + y * myWidth];
                    else
                        if (x == 0)
                            tan[x + y * myWidth] = verts[x + y * myWidth] - verts[x + 1 + y * myWidth];
                        else
                            tan[x + y * myWidth] = verts[x - 1 + y * myWidth] - verts[x + y * myWidth];

                    tan[x + y * myWidth].Normalize();

                    // Normals
                    Vector3 normX = Vector3.one;
                    Vector3 normZ = Vector3.one;

                    if (x != 0 && x < myWidth - 1)
                        normX = new Vector3((verts[x + 1 + y * myWidth].y - verts[x - 1 + y * myWidth].y) / 2, 1, 0);
                    else
                        if (x == 0)
                            normX = new Vector3((verts[x + 1 + y * myWidth].y - verts[x + y * myWidth].y) / 2, 1, 0);
                        else
                            normX = new Vector3((verts[x + y * myWidth].y - verts[x - 1 + y * myWidth].y) / 2, 1, 0);

                    if (y != 0 && y < myHeight - 1)
                        normZ = new Vector3(0, 1, (verts[x + (y + 1) * myWidth].y - verts[x + (y - 1) * myWidth].y) / 2);
                    else
                        if (y == 0)
                            normZ = new Vector3(0, 1, (verts[x + (y + 1) * myWidth].y - verts[x + y * myWidth].y) / 2);
                        else
                            normZ = new Vector3(0, 1, (verts[x + (y) * myWidth].y - verts[x + (y - 1) * myWidth].y) / 2);

                    normals[x + y * myWidth] = normX + normZ;
                    normals[x + y * myWidth].Normalize();
                }


            meshFilter.sharedMesh.vertices = verts;
            meshFilter.sharedMesh.tangents = tan;
            meshFilter.sharedMesh.normals = normals;
            meshFilter.sharedMesh.uv = uv;
            meshFilter.sharedMesh.colors = col;

            // Index
            int[] terrainIndices = new int[(myWidth - 1) * (myHeight - 1) * 6];
            for (int x = 0; x < myWidth - 1; x++)
            {
                for (int y = 0; y < myHeight - 1; y++)
                {
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = ((x + 1) + (y + 1) * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = ((x + 1) + y * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (x + y * myWidth);

                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = ((x + 1) + (y + 1) * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (x + y * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6] = (x + (y + 1) * myWidth);
                }
            }

            meshFilter.sharedMesh.triangles = terrainIndices;

            cnt = verts.Length;
        }
        else
        {

            // Verts
            Vector3[] verts = new Vector3[4];

            verts[0] = new Vector3(-1, 0, -1);
            verts[1] = new Vector3(1, 0, -1);
            verts[2] = new Vector3(-1, 0, 1);
            verts[3] = new Vector3(1, 0, 1);

            meshFilter.sharedMesh.vertices = verts;

            // Index
            int[] index = new int[6];

            index[0] = 0;
            index[1] = 2;
            index[2] = 1;

            index[3] = 2;
            index[4] = 3;
            index[5] = 1;

            meshFilter.sharedMesh.triangles = index;

            // Normals
            Vector3[] normals = new Vector3[4];

            normals[0] = Vector3.up;
            normals[1] = Vector3.up;
            normals[2] = Vector3.up;
            normals[3] = Vector3.up;

            meshFilter.sharedMesh.normals = normals;

            // UV
            Vector2[] uv = new Vector2[4];

            uv[0] = new Vector2(0, 0);
            uv[1] = new Vector2(1, 0);
            uv[2] = new Vector2(0, 1);
            uv[3] = new Vector2(1, 1);

            uv[0] = Vector3.zero;
            uv[1] = Vector3.zero;
            uv[2] = Vector3.zero;
            uv[3] = Vector3.zero;

            meshFilter.sharedMesh.uv = uv;
        }

        MeshCollider mc = GetComponent<MeshCollider>();

        if (mc != null)
            mc.sharedMesh = meshFilter.sharedMesh;

        

        SetLast();
    }

	// Use this for initialization
	void Start () 
    {
	
	}
	
	// Update is called once per frame
	void Update () 
    {
        if (lastMap != HeightMap || lastmin != min || lastmax != max || lasttxMod != txMod || 
            dirtLastBlend != DirtBlend ||
            dirtLastHeight != DirtHeight ||
            grassLastBlend != GrassBlend ||
            grassLastHeight != GrassHeight ||
            stoneLastHeight != StoneHeight ||
            stoneLastBlend != StoneBlend ||
            snowLastHeight != SnowHeight ||
            snowLastBlend != SnowBlend)
            GenerateTerrain();
	}
    int cnt;
    void OnGUI()
    {
        string str = string.Empty;

        str = string.Format("min: {0}\nmax{1}\n{2}\n{3} x {4}", min, max, cnt, myWidth, myHeight);

        //for (int x = 0; x < 1; x++)
        //{
        //    for (int y = 0; y < height.GetLength(1); y++)
        //    {
        //        str += string.Format("[{0}],", height[x, y]);
        //    }
        //    str += "\n";
        //}
        //GUI.Label(new Rect(0, 0, 100, 1000), str);
    }
}
